package SellerAgent;

import Ontologies.*;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonParser;
import jade.content.ContentElement;
import jade.content.lang.Codec;
import jade.content.lang.sl.SLCodec;
import jade.content.onto.Ontology;
import jade.content.onto.OntologyException;
import jade.content.onto.basic.Action;
import jade.core.Agent;
import jade.core.behaviours.Behaviour;
import jade.core.behaviours.OneShotBehaviour;
import jade.core.behaviours.ParallelBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.domain.FIPAException;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;

import java.io.*;
import java.util.*;

/**
 * Created with IntelliJ IDEA.
 * User: jiakai.lian
 * Date: 13-5-9
 * Time: 10:43 PM
 * this seller agent present sellers in the system
 * sellers can modify their selling list via the gui provided.
 * After start selling, this agent can response query request and perform purchase request from buyer agents
 *
 * <p/>
 * Edited Duy Han Phan
 * 14-05-2013: SERVICE_TYPE
 */
public class Seller extends Agent implements FruitTreeVocabulary {
    /**
     * the predefined protocol language
     */
    private final Codec codec = new SLCodec();
    /**
     * the reference of ontology
     */
    private final Ontology ontology = FruitTreeNurserySystemOntology.getInstance();
    /**
     * the gui for seller
     */
    private SellerGUI gui;
    /**
     * the gui to show selling report
     */
    private SellingReportGUI reportGUI;
    /**
     * a list of trees for selling
     */
    private List<TreeWithPrice> sellingList = null;
    /**
     * a list of all types of trees
     */
    private List<TreeWithPrice> allTreesList = null;
    /**
     * a list of transaction record
     */
    private List<Transaction> transactionList = new ArrayList<Transaction>();
    /**
     * a list of tree names which still in processing
     * used to deal concurrency
     */
    private final Set<String> inProcessingTrees = new HashSet<String>();
    /**
     * the flag to exit
     */
    private final boolean finished = false;

    /**
     * a parallel behaviour, all requests will be performed in its sub-behaviour concurrently
     */
    private final ParallelBehaviour parallelBehaviour = new ParallelBehaviour(this, ParallelBehaviour.WHEN_ALL);

    public List<Transaction> getTransactionList() {
        return transactionList;
    }

    public void setTransactionList(List<Transaction> transactionList) {
        this.transactionList = transactionList;
    }

    public List<TreeWithPrice> getAllTreesList() {
        return allTreesList;
    }

    public List<TreeWithPrice> getSellingList() {
        return sellingList;
    }

    public void setSellingList(List<TreeWithPrice> sellingList) {
        this.sellingList = sellingList;
    }

    /**
     *  This setup method initial the agent before take any actions
     * @see jade.core.Agent#addBehaviour(jade.core.behaviours.Behaviour b)
     * @see jade.core.behaviours.Behaviour
     */
    @Override
    protected void setup() {
        Object[] args = getArguments();


        // Register language and ontology
        getContentManager().registerLanguage(codec);
        getContentManager().registerOntology(ontology);

        //is this a good way to do it? not sure
        allTreesList = ((FruitTreeNurserySystemOntology) ontology).getAllTreesList();
        sellingList = ReadSellingListFromFile();
        gui = new SellerGUI(this);
        reportGUI = new SellingReportGUI(this);


        if (args != null && args.length != 0 && args[0].toString().equalsIgnoreCase("No GUI")) {
            sellingList.add(allTreesList.get(0));//for test
            //not show gui, start to listening request directly
            StartSelling();
        } else {
            addBehaviour(new ShowGUIBehaviour(this));
        }

    }

    /**
     * register service for this seller agent
     * @param serviceType  the type of service
     * @return  registration successful or not
     */
    private boolean RegisterService(String serviceType) {
        // Register the tree selling service in the yellow pages
        DFAgentDescription dfd = new DFAgentDescription();
        dfd.setName(getAID());
        ServiceDescription sd = new ServiceDescription();

        sd.setType(serviceType);
        sd.setName(getName());
        dfd.addServices(sd);
        try {
            DFService.register(this, dfd);
            System.out.println(getLocalName() + " register service successfully");
            return true;

        } catch (FIPAException fe) {
            fe.printStackTrace();
            return false;
        }
    }

    /**
     * start selling trees
     */
    public void StartSelling() {
        //register service,service type depends on the tree's category of selling list
        if (!RegisterService(sellingList.get(0).getCategory() + SELLING)) {
            //quit
            doQuitAction("Cannot register service!");
            return;
        }
        System.out.println(getLocalName() + " start listening");

        //start to listen request
        parallelBehaviour.addSubBehaviour(new ListenRequestBehaviour(this));
        addBehaviour(parallelBehaviour);

        //hide seller gui, show report gui
        gui.setVisible(false);
        reportGUI.setVisible(true);
    }

    /**
     *  plan to destroy this agent
     * @param msg  the message want to show in console
     */
    public void doQuitAction(String msg) {
        System.out.println(msg);
        addBehaviour(new QuitBehaviour(this));
    }

    /**
     *  read selling list from json file
     *  files are under /data/sellers/SellingList directory
     */
    public List<TreeWithPrice> ReadSellingListFromFile() {
        List<TreeWithPrice> trees = new ArrayList<TreeWithPrice>();
        Gson gson = new Gson();
        File sellingListFile = new File("data/sellers/SellingList/" + getLocalName() + "-SellingList" + ".json");
        if (sellingListFile.exists()) {
            BufferedReader br;
            try {
                br = new BufferedReader(new FileReader(sellingListFile));
                String line;
                StringBuilder strBuilder = new StringBuilder();
                while ((line = br.readLine()) != null) {
                    strBuilder.append(line);
                }
                String json = strBuilder.toString();//.replaceAll("[\\s]+", "");
                JsonParser parser = new JsonParser();
                JsonArray array = parser.parse(json).getAsJsonArray();
                for (int i = 0; i < array.size(); i++) {
                    TreeWithPrice tree = gson.fromJson(array.get(i), TreeWithPrice.class);
                    trees.add(tree);
                }
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return trees;
    }

    /**
     * save the selling list to into file based on json format
     * @param sellingList   the selling list want to save
     */
    public void saveSellingListToFile(List<TreeWithPrice> sellingList) {
        Gson gson = new Gson();
        File sellingListFile = new File("data/sellers/" + getLocalName() + "-SellingList" + ".json");

        setSellingList(sellingList);
        try {
            if (sellingListFile.exists()) {
                //if the file already exist, delete it first.
                sellingListFile.delete();
            }

            BufferedWriter bw = new BufferedWriter(new FileWriter(sellingListFile));
            String json = gson.toJson(sellingList);
            bw.write(json);
            bw.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * the behaviour to show the seller gui
     */
    private class ShowGUIBehaviour extends OneShotBehaviour {
        public ShowGUIBehaviour(Agent a) {
            super(a);
        }

        @Override
        public void action() {
            System.out.println(getLocalName() + " show GUI");

            gui.showGui();
        }
    }

    /**
     * the behaviour to monitor requests from buyers
     */
    private class ListenRequestBehaviour extends Behaviour {

        public static final int SLEEP_TIME = 50;

        public ListenRequestBehaviour(Agent a) {
            super(a);
        }

        @Override
        public void action() {
            MessageTemplate template = MessageTemplate.MatchPerformative(ACLMessage.REQUEST);

            //do {

            ACLMessage msg = receive(template);

            if (msg != null) {
                if (msg.getPerformative() != ACLMessage.REQUEST) {
                    System.out.println("\nUnexpected msg from buyer!");
                    // continue;
                }
                System.out.println("\n" + getLocalName() + " receive " + msg.toString()); //for test purpose
                try {
                    ContentElement content = getContentManager().extractContent(msg);

                    if (content instanceof Action) {
                        Action actionInMsg = (Action) content;

                        if (actionInMsg.getAction() instanceof PurchaseAction) {
                            //if the request is a purchase request

                            PurchaseAction action = (PurchaseAction) actionInMsg.getAction();
                            //aim to deal with concurrency
                            //if this type of tree is already in processing, wait until the previous request finished.
                            while (inProcessingTrees.contains(action.getCommonName())) {
                                try {
                                    Thread.sleep(SLEEP_TIME);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }
                            //add the tree name into the list before performing
                            inProcessingTrees.add(action.getCommonName());
                            parallelBehaviour.addSubBehaviour(new PerformPurchaseBehaviour(myAgent, msg, action, sellingList, inProcessingTrees));
                        } else if (actionInMsg.getAction() instanceof QueryAction) {
                            //if the request is a query request
                            parallelBehaviour.addSubBehaviour(new PerformQueryBehaviour(myAgent, msg, (QueryAction) actionInMsg.getAction(), sellingList));
                        }
                    }
                } catch (Codec.CodecException e) {
                    e.printStackTrace();
                } catch (OntologyException e) {
                    e.printStackTrace();
                }

                if (!finished) {
                    block();
                }

            }
            //} while (!finished);
        }

        @Override
        public boolean done() {
            return finished;
        }
    }

    /**
     * the behaviour to destroy the agent
     */
    private final class QuitBehaviour extends OneShotBehaviour {

        private QuitBehaviour(Agent a) {
            super(a);
        }

        @Override
        public void action() {
            try {
                //deregister service before quit
                DFService.deregister(myAgent);
            } catch (FIPAException e) {
                e.printStackTrace();
            }
            System.out.println(getLocalName() + " is shutting down...Bye!");
            doDelete();
        }
    }

    /**
     * the method to create a new transaction record based on information in parameters
     * @param tree the target tree in purchase procedure
     * @param msg  the message received
     */
    public void CreateNewTransactionRecord(TreeWithPrice tree, ACLMessage msg) {
        Transaction transaction = new Transaction(tree, msg.getSender().getLocalName(), getLocalName(), new Date());

        transactionList.add(transaction);

        //save the new selling list to file
        saveSellingListToFile(sellingList);

        //update corresponding gui
        updateGUI();
    }

    /**
     * update gui,in this case ,only report screen need to be updated
     */
    public void updateGUI() {
        reportGUI.updateTables(sellingList, transactionList);
    }
}
